using System;
using System.Runtime.Remoting;
using System.Collections;
using System.Data.SqlClient;
using System.Collections.Generic;
using System.Data.Sql;

namespace OnlinePayments
{
	public struct CustomerInfo
	{
		public string FirstName;
		public string LastName;
		public string Address;
		public string City;
		public string State;
		public string Zip;
		public string Country;
		public string CardNumber;
		public string CardExpiration;
		public string CVV2;
	}

	/// <summary>
	/// Summary description for Class1.
	/// </summary>
	public class PaymentProcessor
	{
		private PaymentProcessor _paymentProcessor = null;
		protected bool _test = false;
        protected string _className = null;
        protected Dictionary<string, string> _configStrings = new Dictionary<string, string>();

        public static int ProcessTransaction(SqlConnection conn, int transactionID)
        {
            if (conn.State != System.Data.ConnectionState.Open)
                conn.Open();

            SqlCommand cmm = conn.CreateCommand();
            cmm.CommandText = "select tr.TransactionID, tr.TransactionTypeCode, tr.ProcessorCode, tr.Amount, dbo.Decrypt(acc.AccountNumber), acc.CardExpiration, oh.OwnerID,\n" +
                "   per.FirstName, per.LastName, addr.Address1, addr.City, addr.State, addr.PostalCode, cou.Name,\n" +
                "   tr.TransactionStatusCode, tr.ProcessorTransactionID, tr.CardAuthorization, tr.RelatedTransactionID, tr2.ProcessorTransactionID\n" +
                "from tblTransaction tr\n" +
                "   join tblAccount acc on tr.AccountID = acc.AccountID\n" +
                "   join tblOrderHeader oh on tr.OrderID = oh.OrderID\n" +
                "   join tblCustomer cus on oh.CustomerID = cus.CustomerID\n" +
                "   join tblPerson per on cus.PersonID = per.PersonID\n" +
                "   join tblAddress addr on per.AddressID = addr.AddressID\n" +
                "   join codeCountry cou on addr.CountryID = cou.CountryID\n" +
                "   left outer join tblTransaction tr2 on tr.RelatedTransactionID = tr2.TransactionID\n" +
                "where tr.TransactionStatusCode in ('E', 'A')\n" +
                "   and acc.AccountNumber is not null\n" +
                "   and tr.TransactionID = " + transactionID.ToString();
            SqlDataReader rdr = cmm.ExecuteReader();
            if (rdr.Read())
            {
                CustomerInfo ci = new CustomerInfo();
                if (!rdr.IsDBNull(7))
                    ci.FirstName = rdr.GetString(7).Trim();
                if (!rdr.IsDBNull(8))
                    ci.LastName = rdr.GetString(8).Trim();
                if (!rdr.IsDBNull(9))
                    ci.Address = rdr.GetString(9).Trim();
                if (!rdr.IsDBNull(10))
                    ci.City = rdr.GetString(10).Trim();
                if (!rdr.IsDBNull(11))
                    ci.State = rdr.GetString(11).Trim();
                if (!rdr.IsDBNull(12))
                    ci.Zip = rdr.GetString(12).Trim();
                if (!rdr.IsDBNull(13))
                    ci.Country = rdr.GetString(13).Trim();
                if (!rdr.IsDBNull(4))
                    ci.CardNumber = rdr.GetString(4).Trim();
                if (!rdr.IsDBNull(5))
                    ci.CardExpiration = rdr.GetString(5).Trim();

                string processorCode = rdr.GetString(2);
                int ownerID = rdr.GetInt32(6);
                string transactionTypeCode = rdr.GetString(1);
                string transactionStatusCode = rdr.GetString(14);
                PaymentProcessor processor = CreatePaymentProcessor(conn, processorCode[0], ownerID);

                string responseCode = string.Empty, responseText = string.Empty, AVSResult = string.Empty, CVV2Response = string.Empty,
                    processorTransactionID = string.Empty, authCode = string.Empty;
                char result = 'E';

                if (transactionTypeCode == "B" && transactionStatusCode == "E")
                {
                    result = processor.Deposit(ci, transactionID, rdr.GetDecimal(3), out responseCode, out responseText, out AVSResult, out CVV2Response, out processorTransactionID);
                }
                if (transactionTypeCode == "B" && transactionStatusCode == "A")
                {
                    processorTransactionID = rdr.GetString(15);
                    authCode = rdr.GetString(16);
                    result = processor.DepositWithPriorAuth(ci, transactionID, rdr.GetDecimal(3), processorTransactionID, authCode, out responseCode, out responseText);
                }
                if (transactionTypeCode == "R")
                {
                    result = processor.Refund(ci, transactionID, rdr.GetDecimal(3), rdr.GetString(18), out responseCode, out responseText, out processorTransactionID);
                }
                rdr.Close();

                // Make sure that the transaction response is in the codeTransactionResponse table
                cmm.CommandText = "insert into codeTransactionResponse ( ProcessorCode, ResponseCode, Description, Retry )\n" +
                    "select @ProcessorCode, @ResponseCode, @Description, 0\n" +
                    "where not exists ( select 1 from codeTransactionResponse where ProcessorCode = @ProcessorCode and ResponseCode = @ResponseCode )";
                cmm.Parameters.Clear();
                cmm.Parameters.Add(new SqlParameter("@ProcessorCode", processorCode));
                cmm.Parameters.Add(new SqlParameter("@ResponseCode", responseCode));
                cmm.Parameters.Add(new SqlParameter("@Description", responseText ?? string.Empty));
                cmm.ExecuteNonQuery();

                // Create a new tblTransactionSubmit record
                cmm.CommandText = "insert into tblTransactionSubmit ( TransactionID, TransactionResponseID, TransactionResponse, TransactionSubmitTypeCode, Decision, AVSResponse, CVV2Response, CreateDate, ResponseDate, SubmissionCount )\n" +
                    "select @TransactionID, TransactionResponseID, coalesce(@Description, Description), 'D', @Decision, @AVSResponse, @CVV2Response, getdate(), getdate(), 0\n" +
                    "from codeTransactionResponse \n" +
                    "where ProcessorCode = @ProcessorCode and ResponseCode = @ResponseCode;\n" +
                    "select SCOPE_IDENTITY()";
                cmm.Parameters.Clear();
                cmm.Parameters.Add(new SqlParameter("@TransactionID", transactionID));
                cmm.Parameters.Add(new SqlParameter("@AvsResponse", AVSResult ?? string.Empty));
                cmm.Parameters.Add(new SqlParameter("@CVV2Response", CVV2Response ?? string.Empty));
                cmm.Parameters.Add(new SqlParameter("@ProcessorCode", processorCode));
                cmm.Parameters.Add(new SqlParameter("@ResponseCode", responseCode));
                cmm.Parameters.Add(new SqlParameter("@Decision", result));
                cmm.Parameters.Add(new SqlParameter("@Description", string.IsNullOrEmpty(responseText) ? DBNull.Value : (object)responseText));
                int transactionSubmitID = (int)(decimal)cmm.ExecuteScalar();

                if (result == 'A') // Accepted
                {
                    cmm.CommandText = "update tblTransaction set TransactionStatusCode = 'P',\n" +
                        "   ProcessorTransactionID=@ProcessorTransactionID\n" +
                        "where TransactionID = @TransactionID";
                    cmm.Parameters.Clear();
                    cmm.Parameters.Add(new SqlParameter("@ProcessorTransactionID", processorTransactionID));
                    cmm.Parameters.Add(new SqlParameter("@CardAuthorization", authCode));
                    cmm.Parameters.Add(new SqlParameter("@TransactionID", transactionID));
                    cmm.ExecuteNonQuery();

                    if (transactionTypeCode == "B")
                    {
                        cmm.CommandText = "update oh set OrderStatusCode = 'E'\n" +
                            "from tblOrderHeader oh\n" +
                            "   join tblTransaction tr on oh.OrderID = tr.OrderID\n" +
                            "where oh.OrderStatusCode = 'P'\n" +
                            "   and tr.TransactionID = @TransactionID";
                        cmm.Parameters.Clear();
                        cmm.Parameters.Add(new SqlParameter("@TransactionID", transactionID));
                        cmm.ExecuteNonQuery();
                    }
                }
                else if (result == 'D') // Declined
                {
                    cmm.CommandText = "update tr set TransactionStatusCode = 'F'\n" +
                        "from tblTransaction tr,\n" +
                        "   codeTransactionResponse ctr\n" +
                        "where tr.TransactionID = @TransactionID\n" +
                        "   and ctr.ProcessorCode = @ProcessorCode and ctr.ResponseCode = @ResponseCode\n" +
                        "   and ctr.Retry = 0";

                    cmm.Parameters.Clear();
                    cmm.Parameters.Add(new SqlParameter("@TransactionID", transactionID));
                    cmm.Parameters.Add(new SqlParameter("@ProcessorCode", processorCode));
                    cmm.Parameters.Add(new SqlParameter("@ResponseCode", responseCode));
                    cmm.ExecuteNonQuery();
                }
                return transactionSubmitID;
            }
            return 0;
        }


        public static int AuthorizeTransaction(SqlConnection conn, int transactionID, string cvv2)
        {
            if (conn.State != System.Data.ConnectionState.Open)
                conn.Open();

            SqlCommand cmm = conn.CreateCommand();
            cmm.CommandText = "select tr.TransactionID, tr.TransactionTypeCode, tr.ProcessorCode, tr.Amount, dbo.Decrypt(acc.AccountNumber), acc.CardExpiration, oh.OwnerID,\n" +
                "   per.FirstName, per.LastName, addr.Address1, addr.City, addr.State, addr.PostalCode, cou.Name\n" +
                "from tblTransaction tr\n" +
                "   join tblAccount acc on tr.AccountID = acc.AccountID\n" +
                "   join tblOrderHeader oh on tr.OrderID = oh.OrderID\n" +
                "   join tblCustomer cus on oh.CustomerID = cus.CustomerID\n" +
                "   join tblPerson per on cus.PersonID = per.PersonID\n" +
                "   join tblAddress addr on per.AddressID = addr.AddressID\n" +
                "   join codeCountry cou on addr.CountryID = cou.CountryID\n" +
                "where tr.TransactionStatusCode = 'E'\n" +
                "   and tr.TransactionTypeCode = 'B'\n" +
                "   and tr.TransactionID = " + transactionID.ToString();
            SqlDataReader rdr = cmm.ExecuteReader();
            if (rdr.Read())
            {
                CustomerInfo ci = new CustomerInfo();
                ci.FirstName = rdr.GetString(7).Trim();
                ci.LastName = rdr.GetString(8).Trim();
                ci.Address = rdr.GetString(9).Trim();
                ci.City = rdr.GetString(10).Trim();
                ci.State = rdr.GetString(11).Trim();
                ci.Zip = rdr.GetString(12).Trim();
                ci.Country = rdr.GetString(13).Trim();
                ci.CardNumber = rdr.GetString(4).Trim();
                ci.CardExpiration = rdr.GetString(5).Trim();
                ci.CVV2 = cvv2.Trim();

                string processorCode = rdr.GetString(2);
                int ownerID = rdr.GetInt32(6);
                PaymentProcessor processor = CreatePaymentProcessor(conn, processorCode[0], ownerID);
                string responseCode, responseText, AVSResult, CVV2Response, processorTransactionID, authCode;
                char result = processor.Authorize(ci, rdr.GetInt32(0), rdr.GetDecimal(3), out responseCode, out responseText, out AVSResult, out CVV2Response, out processorTransactionID, out authCode );

                rdr.Close();

                // Make sure that the transaction response is in the codeTransactionResponse table
                cmm.CommandText = "insert into codeTransactionResponse ( ProcessorCode, ResponseCode, Description, Retry )\n" +
                    "select @ProcessorCode, @ResponseCode, @Description, 0\n" +
                    "where not exists ( select 1 from codeTransactionResponse where ProcessorCode = @ProcessorCode and ResponseCode = @ResponseCode)";
                cmm.Parameters.Clear();
                cmm.Parameters.Add(new SqlParameter("@ProcessorCode", processorCode));
                cmm.Parameters.Add(new SqlParameter("@ResponseCode", responseCode));
                cmm.Parameters.Add(new SqlParameter("@Description", responseText ?? string.Empty));
                cmm.ExecuteNonQuery();

                // Create a new tblTransactionSubmit record
                cmm.CommandText = "insert into tblTransactionSubmit ( TransactionID, TransactionResponseID, TransactionResponse, TransactionSubmitTypeCode, Decision, AVSResponse, CVV2Response, CreateDate, ResponseDate, SubmissionCount )\n" +
                    "select @TransactionID, TransactionResponseID, coalesce(@Description, Description), 'A', @Decision, @AVSResponse, @CVV2Response, getdate(), getdate(), 0\n" +
                    "from codeTransactionResponse \n" +
                    "where ProcessorCode = @ProcessorCode and ResponseCode = @ResponseCode;\n" +
                    "select SCOPE_IDENTITY()";
                cmm.Parameters.Clear();
                cmm.Parameters.Add(new SqlParameter("@TransactionID", transactionID));
                cmm.Parameters.Add(new SqlParameter("@AvsResponse", AVSResult ?? string.Empty));
                cmm.Parameters.Add(new SqlParameter("@CVV2Response", CVV2Response ?? string.Empty));
                cmm.Parameters.Add(new SqlParameter("@ProcessorCode", processorCode));
                cmm.Parameters.Add(new SqlParameter("@ResponseCode", responseCode));
                cmm.Parameters.Add(new SqlParameter("@Decision", result));
                cmm.Parameters.Add(new SqlParameter("@Description", (object)responseText ?? DBNull.Value));
                int TransactionSubmitID = (int)(decimal)cmm.ExecuteScalar();

                if (result == 'A') // Accepted
                {
                    cmm.CommandText = "update tblTransaction set TransactionStatusCode = 'A',\n" +
                        "   ProcessorTransactionID=@ProcessorTransactionID,\n" +
                        "   CardAuthorization=@CardAuthorization,\n" +
                        "   CardAuthorizationDate=getdate()\n" +
                        "where TransactionID = @TransactionID";
                    cmm.Parameters.Clear();
                    cmm.Parameters.Add(new SqlParameter("@ProcessorTransactionID", processorTransactionID ?? string.Empty));
                    cmm.Parameters.Add(new SqlParameter("@CardAuthorization", authCode ?? string.Empty));
                    cmm.Parameters.Add(new SqlParameter("@TransactionID", transactionID));
                    cmm.ExecuteNonQuery();
                }
                else if (result == 'D') // Declined
                {
                    cmm.CommandText = "update tblTransaction set TransactionStatusCode = 'F' where TransactionID = @TransactionID";
                    cmm.Parameters.Clear();
                    cmm.Parameters.Add(new SqlParameter("@TransactionID", transactionID));
                    cmm.ExecuteNonQuery();
                }

                return TransactionSubmitID;
            }

            return 0;
        }


        public static char CancelAuthorization(SqlConnection conn, int transactionID)
        {
            char result = 'E';

            if (conn.State != System.Data.ConnectionState.Open)
                conn.Open();

            SqlCommand cmm = conn.CreateCommand();
            cmm.CommandText = "select tr.TransactionID, tr.TransactionTypeCode, tr.ProcessorCode, tr.Amount, dbo.Decrypt(acc.AccountNumber), acc.CardExpiration, oh.OwnerID,\n" +
                "   tr.ProcessorTransactionID\n" +
                "from tblTransaction tr\n" +
                "   join tblAccount acc on tr.AccountID = acc.AccountID\n" +
                "   join tblOrderHeader oh on tr.OrderID = oh.OrderID\n" +
                "   join tblCustomer cus on oh.CustomerID = cus.CustomerID\n" +
                "where tr.TransactionStatusCode = 'A'\n" +
                "   and tr.TransactionTypeCode = 'B'\n" +
                "   and tr.TransactionID = " + transactionID.ToString();
            SqlDataReader rdr = cmm.ExecuteReader();
            if (rdr.Read())
            {
                string processorCode = rdr.GetString(2);
                int ownerID = rdr.GetInt32(6);
                PaymentProcessor processor = CreatePaymentProcessor(conn, processorCode[0], ownerID);
                string responseCode, responseText;
                string processorTransactionID = rdr.GetString(7);
                decimal amount = rdr.GetDecimal(3);

                result = processor.CancelAuthorization(transactionID,
                    processorTransactionID,
                    amount,
                    out responseCode,
                    out responseText);

                rdr.Close();

                // Make sure that the transaction response is in the codeTransactionResponse table
                cmm.CommandText = "insert into codeTransactionResponse ( ProcessorCode, ResponseCode, Description, Retry )\n" +
                    "select @ProcessorCode, @ResponseCode, @Description, 0\n" +
                    "where not exists ( select 1 from codeTransactionResponse where ProcessorCode = @ProcessorCode and ResponseCode = @ResponseCode)";
                cmm.Parameters.Clear();
                cmm.Parameters.Add(new SqlParameter("@ProcessorCode", processorCode));
                cmm.Parameters.Add(new SqlParameter("@ResponseCode", responseCode));
                cmm.Parameters.Add(new SqlParameter("@Description", responseText ?? string.Empty));
                cmm.ExecuteNonQuery();

                if (result == 'A') // Accepted
                {
                    cmm.CommandText = "update tblTransaction set TransactionStatusCode = 'X',\n" +
                        "where TransactionID = @TransactionID";
                    cmm.Parameters.Clear();
                    cmm.Parameters.Add(new SqlParameter("@TransactionID", transactionID));
                    cmm.ExecuteNonQuery();
                }
            }

            return result;
        }

        private static PaymentProcessor CreatePaymentProcessor(SqlConnection conn, char processorCode, int ownerID)
        {
            SqlCommand cmm = conn.CreateCommand();
            cmm.CommandText = "select ClassName from codePaymentProcessor where ProcessorCode = '" + processorCode + "'";
            SqlDataReader rdr = cmm.ExecuteReader();
            if (!rdr.Read())
                throw new Exception("Missing ClassName for payment system " + processorCode);
            string className = rdr.GetString(0);
            rdr.Close();

            className = "OnlinePayments." + className;
            PaymentProcessor pr = (PaymentProcessor)System.Activator.CreateInstance(Type.GetType(className), new object[] { conn, processorCode, ownerID });
            return pr;
            //ObjectHandle handle = System.Activator.CreateInstance(null,
            //    (string)className,
            //    true,
            //    0,
            //    null,
            //    new object[] { conn, processorCode, ownerID },
            //    null, null, null);

            //if (handle == null)
            //    throw new Exception("Error creating payment processor class " + className);
            //return (PaymentProcessor)handle.Unwrap();
        }

		public PaymentProcessor( SqlConnection conn, char processorCode, int ownerID ) 
		{
            SqlCommand cmm = conn.CreateCommand();
            cmm.CommandText = "select Name, dbo.Decrypt(Value) from tblPaymentProcessorConfig where ProcessorCode = '" + processorCode + "' and OwnerID = " + ownerID.ToString();
            SqlDataReader rdr = cmm.ExecuteReader();
            _configStrings = new Dictionary<string, string>();
            while (rdr.Read())
                _configStrings.Add(rdr.GetString(0), rdr.GetString(1));
            rdr.Close();

            _test = _configStrings.ContainsKey("Test");
        }

		public virtual char Authorize( CustomerInfo customerInfo, 
			Int32 transactionID, 
			decimal amount, 
			out string responseCode,
			out string responseText,
			out string AVSResult,
			out string CVV2Response,
			out string processorTransactionID,
			out string authCode 
			)
		{
			return _paymentProcessor.Authorize( 
				customerInfo, 
				transactionID, 
				amount, 
				out responseCode, 
				out responseText, 
				out AVSResult,
				out CVV2Response,
				out processorTransactionID,
				out authCode );
		}

        public virtual char CancelAuthorization(Int32 transactionID,
            string processorTransactionID,
            decimal amount,
            out string responseCode,
            out string responseText
            )
        {
            return _paymentProcessor.CancelAuthorization(
                transactionID,
                processorTransactionID,
                amount,
                out responseCode,
                out responseText);
        }

        public virtual char Deposit(CustomerInfo customerInfo, 
			Int32 transactionID, 
			decimal amount, 
			out string responseCode,
			out string responseText,
			out string AVSResult,
			out string CVV2Response,
			out string processorTransactionID 
			)
		{
			return _paymentProcessor.Deposit(
				customerInfo, 
				transactionID, 
				amount, 
				out responseCode,
				out responseText,
				out AVSResult,
				out CVV2Response,
				out processorTransactionID );
		}

        public virtual char DepositWithPriorAuth(CustomerInfo customerInfo, 
			Int32 transactionID, 
			decimal amount, 
			string processorTransactionID,
			string authCode,
			out string responseCode,
			out string responseText
			)
		{
			return _paymentProcessor.DepositWithPriorAuth(
				customerInfo,
				transactionID, 
				amount, 
				processorTransactionID,
				authCode,
				out responseCode,
				out responseText );
		}

        public virtual char Refund(CustomerInfo customerInfo,
            Int32 transactionID,
            decimal amount,
            string priorProcessorTransactionID,
            out string responseCode,
            out string responseText,
            out string processorTransactionID
			)
		{
			return _paymentProcessor.Refund(
				customerInfo, 
				transactionID, 
				amount, 
				priorProcessorTransactionID,
				out responseCode,
				out responseText,
                out processorTransactionID );
		}
	}
}
